package com.mapforge.map.tools.selection;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import com.mapforge.map.TileLayer;
import com.mapforge.map.tilewriter.TileWriter;
import com.mapforge.map.tools.AbstractMapTool;
import com.mapforge.selection.ContinuousSelection;
import com.mapforge.selection.NullSelectionShapeModifier;
import com.mapforge.selection.Selection;
import com.mapforge.selection.SelectionModifier;
import com.mapforge.selection.SetOperationType;

/**
 * The {@link AbstractSelectionTool} is used to create types of
 * {@link Selection} which can be defined first by two points, and then further
 * approximated if/when necessary.
 * 
 * For example, a rectangular selection tool could create a rectangular
 * selection based solely on two points. However, an elliptical selection tool
 * could use the two points to define a bounding rectangle, but would then need
 * to better approximate an ellipse.
 * 
 * Created 7/05/10
 * 
 * @author Will Morrison
 * @see Selection
 * @see MapTool
 */
public abstract class AbstractSelectionTool extends AbstractMapTool implements
		KeyListener {

	/**
	 * Modifies this {@link Selection} before determining valid points.
	 */
	private SelectionModifier selectionModifier;
	/**
	 * {@link MaskWriter} used to edit Mask
	 */
	private TileWriter maskWriter;

	private TileLayer tileLayer;

	private SetOperationType selectionType;

	/**
	 * Selection created during the last
	 * {@link AbstractSelectionTool#makeSelection()} call.
	 */
	private Selection lastSelection;

	public AbstractSelectionTool(String description) {
		super(description);
		this.selectionModifier = NullSelectionShapeModifier.getHandle();
		this.selectionType = SetOperationType.NEW;
	}

	public void setSelectionType(SetOperationType setOperationType) {
		this.selectionType = setOperationType;
	}

	public SetOperationType getSelectionType() {
		return this.selectionType;
	}

	public void setTileLayer(TileLayer layer) {
		this.tileLayer = layer;
	}

	public TileLayer getTileLayer() {
		return tileLayer;
	}

	/**
	 * Sets this {@link AbstractSelectionTool} {@link MaskWriter}.
	 * 
	 * @param writer
	 *            new mask writer for this tool
	 */
	public void setTileWriter(TileWriter writer) {
		this.maskWriter = writer;
	}

	/**
	 * Returns this {@link AbstractSelectionToool} {@link MaskWriter}.
	 * 
	 * @return this selection tool's MaskWriter
	 */
	public TileWriter getTileWriter() {
		return this.maskWriter;
	}

	/**
	 * Returns the last known {@link Selection} made with this
	 * {@link AbstractSelectionTool} using its defined anchor and moving points.
	 * 
	 * @see AbstractSelectionTool#makeSelection()
	 * @return last {@link Selection} made with this
	 *         {@link AbstractSelectionTool}
	 */
	public Selection getCurrentSelection() {
		return this.lastSelection;
	}

	public void setCurrentSelection(Selection selection) {
		this.lastSelection = selection;
	}

	public void setSelectionModifier(SelectionModifier shapeModifier) {
		this.selectionModifier = shapeModifier;
	}

	public SelectionModifier getSelectionModifier() {
		return selectionModifier;
	}

	/**
	 * Creates a {@link Selection} based on this {@link AbstractSelectionTool}
	 * anchor point and moving point. The tool's current selection will be
	 * created by calling
	 * {@link AbstractSelectionTool#performSelection(Point, Point)} with the
	 * tool's current anchor point, and moving point.
	 * 
	 */
	public void makeSelection() {
		Point startPoint = (Point) getAnchorPoint().clone();
		Point currentPoint = (Point) getMovingPoint().clone();
		// shift points with modifier before selection is made
		SelectionModifier selectionMod = getSelectionModifier();
		selectionMod.modifySelection(startPoint, currentPoint);
		// make the selection
		this.lastSelection = performSelection(startPoint, currentPoint);
		int tw = this.getTileMap().getTotalHorizontalTiles();
		int th = this.getTileMap().getTotalVerticalTiles();
		// ensure all points are within bounds
		Rectangle rect = new Rectangle(0, 0, tw, th);
		lastSelection.intersect(new ContinuousSelection(rect));
	}

	public void keyPressed(KeyEvent e) {
	}

	public void keyTyped(KeyEvent e) {

	}

	public void keyReleased(KeyEvent e) {
	}

	/**
	 * Should determine which points fall within this selection based on an
	 * anchor point, and a moving point.
	 * 
	 * @param anchorPoint
	 *            first coordinate, usually doesn't move
	 * @param movingPoint
	 *            second point which can change often
	 * @return selection defined using, in part, the anchor and moving points
	 */
	public abstract Selection performSelection(Point anchorPoint,
			Point movingPoint);

	public SelectionToolState getState() {
		return new SelectionToolState(getAnchorPoint(), getMovingPoint());
	}
}
